GRF
CreatedFiles are saved in little-endian.
Header ¶
Label | Bytes | Type | Description |
---|---|---|---|
signature | 16 | char | 'Master of Magic\0' |
encryption | 14 | ubyte | Determines if encryption is allowed. If set to [0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e] then encryption is on. If all 14 bytes are null then it is not. |
offset | 4 | uint32 | Offset of the Filetable after the header (offset + 46 ). |
seed | 4 | uint32 | Used as some dummy count for the actual filecount. |
nFiles | 4 | uint32 | Number of files in the GRF (inaccurate). The correct filecount is nFiles - seed - 7 . |
version | 4 | uint32 | Version of the GRF. Known so far: 0x102, 0x103 and 0x200. |
After the header comes the actual data. Somewhere at the bottom at the offset offset + 46
starts the Filetable. Depending on the version the files and or the filetable are either compressed and or encrypted.
Filetable ¶
Version 0x100 - 0x103 ¶
Repeat the following structure nFiles
times:
Label | Bytes | Type | Description |
---|---|---|---|
length | 4 | uint32 | Length that is used for the filename (actual filename length may be shorter). |
filename | length | char | Nibble swapped and possibly encrypted filename. On version >=0x101 the first two bytes are skipped and the filename is encrypted. |
compressed_size | 4 | uint32 | The compressed size. Actual value is compressed_size - size - 715 . The size value is two entries below. |
compressed_size_aligned | 4 | uint32 | Compressed and encrypted size. Actual value is compressed_size_aligned - 37579 . |
size | 4 | uint32 | The decrypted and uncompressed size. |
flags | 1 | ubyte | Bit flags with the following values: File/Compressed (0x01) , Encrypted (0x02) and EncryptedHeader (0x04) .If the file has the extension .gnd, .gat, .act or .str then the flag EncryptedHeader must be set, otherwise Encrypted must be set (regardless of the value in the flags field). |
offset | 4 | uint32 | Offset of the filedata in the GRF after the header (offset + 46 ). |
Version 0x200 ¶
In this version the filetable is compressed (zlib).
Label | Bytes | Type | Description |
---|---|---|---|
compressed_size | 4 | uint32 | Compressed size of the filetable. |
uncompressed_size | 4 | uint32 | Uncompressed size of the filetable |
files | variable | FileEntry | See the file entries below. |
Repeat the following structure nFiles
times:
Label | Bytes | Type | Description |
---|---|---|---|
filename | variable | char | The null terminated filename. |
compressed_size | 4 | uint32 | The compressed size. |
compressed_size_aligned | 4 | uint32 | Compressed and encrypted size. |
size | 4 | uint32 | The decrypted and uncompressed size. |
flags | 1 | ubyte | Bit flags with the following values: File/Compressed (0x01) , Encrypted (0x02) and EncryptedHeader (0x04) . |
offset | 4 | uint32 | Offset of the filedata in the GRF after the header (offset + 46 ). |
Encryption and flags ¶
The possible file flags are:
- File/Compressed (0x01): The "file" is really a file and not a directory. It is also compressed.
- Encrypted (0x02): The file is encrypted using DES.
- EncryptedHeader (0x04): The file is encrypted using Gravity's own mixture of DES and additional byte mangling.
Community edits ¶
LZMA compression instead of deflate (zlib) ¶
rAthena user named curiosity released a patched cps.dll called "LessGRF" (Source code) that uses the LZMA compression algorithm from the 7zip LZMA SDK to compress the files instead of zlib. To know whether the files have been compressed using LZMA an additional null byte is prepended to the file data. That means after you decrypted a file (if it was neccessary) you check if the first byte is null. If it is then use the LZMA decompression algorithm on the rest of the bytes (the null byte is not part of the data) otherwise you use zlib.